<?php

namespace Lib\PdoDb;

use Exception;
use Lib\Util\Util;
use PDO;

/**
 * PdoDb  数据库类
 * 
 * 主要用于创建新的项目时使用
 * 
 * @author: Xialj
 * @date: 2019-08-24
 * @version: v1.0
 * @email: m17269@163.com
 */
class PdoDb extends PDO
{

    private $dbh = "%s:host=%s;port=%s;dbname=%s";
    private $username = null;
    private $password = null;
    private $charset = null;
    private $dbconn = null;
    private $result = null;

    /**
     * 初始化数据库参数,并连接数据库
     * @param string $dataBaseName 数据库名称
     * @param $username 数据库账号
     * @param $password 数据库密码
     */
    public function __construct(array $DbConfig = null, $username = null, $password = null)
    {
        global $CONFIG;
        // 如果没有定义则默认使用项目名对应的数据库
        if (empty($DbConfig)) {
            $pro = $_REQUEST['pro'];
            $DbConfig = $CONFIG['dataBase'][$pro];
        }

        $ms = $DbConfig['ms'];
        $host = $DbConfig['host'];
        $port = $DbConfig['port'];
        $dataBaseName = $DbConfig['databasename'];

        $this->dbh = vsprintf($this->dbh, [$ms, $host, $port, $dataBaseName]);
        $this->username = $username ?? $DbConfig['username'];
        $this->password = $password ?? $DbConfig['password'];
        $this->charset = $DbConfig['charset'];
        try {
            // 连接数据库并设置字符集
            $this->dbconn = new \PDO($this->dbh, $this->username, $this->password);
            $this->dbconn->exec("set names $this->charset");
        } catch (Exception $e) {
            $var = [
                'code' => $CONFIG['others']['dBCode']['insertErrorCode'],
                'msg' => $e->getMessage(),
            ];
            Util::send($var);
        }
    }

    /**
     * 执行sql语句
     * @param sql 需要执行的sql语句
     */
    public function query($sql)
    {
        $this->result = $this->dbconn->query($sql);
        return $this->result;
    }

    // 预处理并执行
    public function pretreatmentAndQuery(string $pretreatmentSQL, string $parameterType, array $sqlArray)
    {
        return self::query(self::pretreatmentSQL($pretreatmentSQL, $parameterType, $sqlArray));
    }

    /**
     * 获取sql内容 返回只有字段名的键值对
     */
    public function selectQueryResult_ASSOC()
    {
        $data = [];
        while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) {
            $data[] = $row;
        }
        return $data;
    }

    /**
     * 预处理函数
     * @param sql 预处理的sql
     * @param sqlArray 准备传入的函数
     * @return string|null  处理好的SQL语句
     */
    public function pretreatmentSQL(string $pretreatmentSQL, string $parameterType, array $sqlArray)
    {
        global $CONFIG;
        $placeholder_count = substr_count($pretreatmentSQL, '%s');
        $array_count = count($sqlArray);

        if ($placeholder_count != $array_count) {
            $var = [
                'code' => $CONFIG['others']['dBCode']['lackParameterErrorCode'],
                'msg' => '参数过多或过少错误'
            ];
            Util::send($var);
        }
        // 验证参数类型，并强制转换为需要的数据，一定程度上避免了SQL注入
        $sqlArray = self::judgeVariableType($parameterType, $sqlArray);
        // 验证通过开始构建sql语句
        $sql = vsprintf($pretreatmentSQL, $sqlArray);
        return $sql;
    }

    /**
     * 验证参数类型，并强制转换
     * a - 检测变量是否为字符串           is_string
     * s - 检测变量是否为字符串           is_string
     * d - 检测变量是否为数字或数字字符串  is_numeric
     * i - 检测变量是否是整数             is_int
     * n - 检测变量是否为NULL             is_null
     * 
     * @param type 上面的类型信息 字符串数组  'ssadin' 的方式传入
     * @param arrayVariable 对应的类型变量数组
     */
    private function judgeVariableType(string $type, array $arrayVariable)
    {
        global $CONFIG;
        $type_len = strlen($type);
        for ($i = 0; $i < $type_len; $i++) {
            if ($type[$i] == 's') {
                // 过滤字符型的数据，避免 sql 注入
                $arrayVariable[$i] = str_replace('\'', '’', $arrayVariable[$i]);
                $arrayVariable[$i] = str_replace('', '', $arrayVariable[$i]);
                $arrayVariable[$i] = str_replace('', '', $arrayVariable[$i]);

                $arrayVariable[$i] = (string) $arrayVariable[$i];
            } elseif ($type[$i] == 'd') {
                $arrayVariable[$i] = doubleval($arrayVariable[$i]);
            } elseif ($type[$i] == 'i') {
                $arrayVariable[$i] = (int) $arrayVariable[$i];
            } elseif ($type[$i] == 'n') {
                $arrayVariable[$i] = null;
            } elseif ($type[$i] == 'a') {
                // 强制转换为 JSON
                $arrayVariable[$i] = json_encode($arrayVariable[$i], JSON_UNESCAPED_UNICODE);
            } else {
                if ($type[$i] != 's' || $type[$i] != 'd' | $type[$i] != 'i' || $type[$i] != 'n') {
                    $msg = '传入参数类型不存在';
                } else {
                    $msg = '数组类型与传入类型不符';
                }
                $var = [
                    'code' => $CONFIG['others']['dBCode']['parameterTypeErrorCode'],
                    'msg' => $msg
                ];
                Util::send($var);
            }
        }
        return $arrayVariable;
    }


    /**
     * 获取错误信息
     */
    public function getErrorInfo()
    {
        if ($this->dbconn->errorCode()) {
            // 只返回错误信息，不返回错误 code
            return $this->dbconn->errorInfo()[2];
        }
    }
}
